package com.sl.sdn.repository.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.sl.sdn.dto.OrganDTO;
import com.sl.sdn.dto.TransportLineNodeDTO;
import com.sl.sdn.entity.enums.OrganTypeEnum;
import com.sl.sdn.entity.node.AgencyEntity;
import com.sl.sdn.repository.TransportLineRepository;
import com.sl.transport.common.util.BeanUtil;
import org.neo4j.driver.internal.value.PathValue;
import org.neo4j.driver.types.Path;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Optional;

@Component
public class TransportLineRepositoryImpl implements TransportLineRepository {

    @Resource
    private Neo4jClient neo4jClient;

    /**
     * 查询两个网点之间最短的路线，查询深度为：10
     *
     * @param start 开始网点
     * @param end   结束网点
     * @return
     */
    @Override
    public TransportLineNodeDTO findShortestPath(AgencyEntity start, AgencyEntity end) {

        // 1.获取标签
        // @Node("AGENCY") 里面的 AGENCY
        String type = AgencyEntity.class.getAnnotation(Node.class).value()[0];

        /*
        查询两个网点之间最短路径，查询深度最大为10
        MATCH path = shortestPath((n:AGENCY) -[*..10]->(m:AGENCY))
        WHERE n.name = "北京市昌平区定泗路" AND m.name = "上海市浦东新区南汇"
        RETURN path
         */

        // 2.创建查询语句
        String cypherQuery = StrUtil.format("MATCH p = shortestPath((start:{})-[*..10]->(end:{})) \n" +
                //
                " WHERE start.bid = $startId AND end.bid = $endId " +
                //
                " RETURN p", type, type);

        // 3.执行查询
        Optional<TransportLineNodeDTO> one = neo4jClient.query(cypherQuery)
                // 下面这两行是将参数绑定到 cypherQuery 中的 $startId 和 $endId
                .bind(start.getBid()).to("startId")
                // 将endId绑定到 cypherQuery 中的 $endId
                .bind(end.getBid()).to("endId")
                // 将它转换为 TransportLineNodeDTO 类型
                .fetchAs(TransportLineNodeDTO.class).mappedBy((typeSystem, record) -> {
                    // 4.获取查询结果
                    // TransportLineNodeDTO
                    TransportLineNodeDTO dto = new TransportLineNodeDTO();
                    // 因为我们求的是最短路线 因此就是一条记录
                    PathValue pathValue = (PathValue) record.get(0);
                    // 获取路线
                    Path path = pathValue.asPath();
                    // 遍历路线中的节点
                    // 读取结点数据
                    path.nodes().forEach(node -> {
                        // node里面存放的数据就是OrganDTO
                        // 需要set一下Type 和 经纬度
                        Map<String, Object> map = node.asMap();
                        OrganDTO organDTO = BeanUtil.toBeanIgnoreError(map, OrganDTO.class);
                        // 设置类型
                        //取第一个标签作为类型
                        // 也就是node.labels()的第一个元素 getCode()就是类型
                        // 也就是获取agency里面的标签值3
                        organDTO.setType(OrganTypeEnum.valueOf(CollUtil.getFirst(node.labels())).getCode());

                        // 设置经纬度
                        organDTO.setLatitude(BeanUtil.getProperty(map.get("location"), "x"));
                        organDTO.setLongitude(BeanUtil.getProperty(map.get("location"), "y"));

                        // 加入到节点列表中
                        dto.getNodeList().add(organDTO);

                    });

                    // 存储cost
                    path.relationships().forEach(relationship -> {
                        // 获取关系数据
                        Map<String, Object> map = relationship.asMap();
                        // 将cost转换为double类型
                        double cost = Convert.toDouble(map.get("cost"), 0d);
                        // 设置cost 也就是路线成本
                        dto.setCost(NumberUtil.add(cost, dto.getCost().doubleValue()));
                    });

                    // 花费的费用保留两个消失
                    dto.setCost(NumberUtil.round(dto.getCost(), 2).doubleValue());
                    return dto;

                }).one();// one就得到一个结果TransportLineNodeDTO

        return one.orElse(null);
    }

    /**
     * 查询两个网点之间花费最少的路线，查询深度为：10
     *
     * @param start 开始网点
     * @param end   结束网点
     * @return
     */
    @Override
    public TransportLineNodeDTO findCostLessPath(AgencyEntity start, AgencyEntity end) {

        // 1.获取到类型
        String type = AgencyEntity.class.getAnnotation(Node.class).value()[0];

        /*
        查询两个网点之间所有的路线中成本最低的路线，最大深度为10（如果成本相同，转运节点最少）
        MATCH path = (n:AGENCY) -[*..10]->(m:AGENCY)
        WHERE n.name = "北京市昌平区定泗路" AND m.name = "上海市浦东新区南汇"
        UNWIND relationships(path) AS r
        WITH sum(r.cost) AS cost, path
        RETURN path ORDER BY cost ASC, LENGTH(path) ASC LIMIT 1
         */

        // 2.编写查询语句
        String cypherQuery = StrUtil.format("MATCH p = (start:{})-[*..10]->(end:{}) \n" +
                //
                " WHERE start.bid = $startId AND end.bid = $endId " +
                //
                " UNWIND relationships(p) AS r " +
                //
                " WITH sum(r.cost) AS cost, p " +
                //
                " RETURN p ORDER BY cost ASC, LENGTH(p) ASC LIMIT 1", type, type);

        // 3.执行查询
        Optional<TransportLineNodeDTO> one = neo4jClient.query(cypherQuery).bind(start.getBid()).to("startId").bind(end.getBid()).to("endId").fetchAs(TransportLineNodeDTO.class).mappedBy((typeSystem, record) -> {

            TransportLineNodeDTO dto = new TransportLineNodeDTO();

            PathValue pathValue = (PathValue) record.get(0);

            Path path = pathValue.asPath();

            path.nodes().forEach(node -> {
                Map<String, Object> map = node.asMap();
                OrganDTO organDTO = BeanUtil.toBeanIgnoreError(map, OrganDTO.class);
                organDTO.setType(OrganTypeEnum.valueOf(CollUtil.getFirst(node.labels())).getCode());
                organDTO.setLatitude(BeanUtil.getProperty(map.get("location"), "x"));
                organDTO.setLongitude(BeanUtil.getProperty(map.get("location"), "y"));
                dto.getNodeList().add(organDTO);
            });

            path.relationships().forEach(relationship -> {
                Map<String, Object> map = relationship.asMap();
                double cost = Convert.toDouble(map.get("cost"), 0d);
                dto.setCost(NumberUtil.add(cost, dto.getCost().doubleValue()));
            });

            // 设置两位消失
            dto.setCost(NumberUtil.round(dto.getCost(), 2).doubleValue());
            return dto;
        }).one();
        return one.orElse(null);
    }
}

